﻿using System;
using System.Drawing;

namespace PlusPlusPaint.Layers.VisualObjects
{
    /// <summary>
    /// Represents a straight line going from one corner of the layer to
    /// another.  The default direction is from the top-left corner to the
    /// bottom-right corner.
    /// </summary>
    class LineSegment : DynamicallyGeneratedVisualObject
    {
        #region member variables
        bool direction; // false is top-left to bottom-right, true is bottom-right to top-left.
        #endregion

        #region constructors
        public LineSegment()
        {

        }

        public LineSegment(Point start, Point end)
        {
            SetBeginAndEnd(start, end);
        }
        #endregion

        #region overrides
        protected override string ObjectTypeName
        {
            get
            {
                return "LineSegment";
            }
        }

        public override SerializedObject Serialize()
        {
            SerializedObject so = base.Serialize();
            so.NamedInts["direction"] = direction ? 1 : 0; // No implicit bool->int, what?
            return so;
        }

        public override void Deserialize(SerializedObject s)
        {
            base.Deserialize(s);
            direction = s.NamedInts["direction"] == 1;
        }

        public override bool PositionIsInHitbox(Point p)
        {
            // Source:  Linear Algebra lectures by professor Beukers
            // Parts of the vector indicating the line
            int lxLineVec = Size.Width; // xV
            int lyLineVec = Size.Height; // yV; flip the line if it's the other way
            int lxClicked = p.X - Location.X; // xC
            int lyClicked = p.Y - Location.Y; // yC
            if (direction)
                lyClicked = this.Size.Height - lyClicked;
            if (lxClicked < 0 || lxClicked > Size.Width || lyClicked < 0 || lyClicked  > Size.Height) // Clearly out of bounds.
                return false;
            // Now, a little on the formula.  There is a line that is perpendicular to
            // the line in the image, and that goes through point p.  We are looking
            // for the coordinates where this line crosses with the line in the image.
            // The directional vector of this line is (lyLineVec, -lxLineVec).  We must
            // solve the equation x(lxLineVec, lyLineVec) - y(lyLineVec, -lxLineVec) =
            // (lxClicked, lyClicked) for x and y.  This is easily done by putting the
            // coefficient matrix in row-echelon form and reading off the result.
            // x = (xV*xC + yV*yC)/(xV^2 + yV^2)
            // y = (xV*yC  yV*xC)/(xV^2 + yV^2)
            // As we have to divide by xV^2 + yV^2 twice, it's best to check that that
            // isn't zero (if it is, the line is a dot, and we may as well just see how
            // far the cursor is).
            float denom = lxLineVec * lxLineVec + lyLineVec * lyLineVec;
            if (denom == 0)
                return lxClicked * lxClicked + lyClicked * lyClicked < (MaximumDistanceToRecogniseOnClick + this.BrushSize / 2);
            float UlCoefficient = (lxLineVec * lxClicked + lyLineVec * lyClicked) / denom;
            int lxIntersect = (int)(UlCoefficient * lxLineVec);
            int lyIntersect = (int)(UlCoefficient * lyLineVec);
            int UlDistance = (lxClicked - lxIntersect) * (lxClicked - lxIntersect) + (lyClicked - lyIntersect) * (lyClicked - lyIntersect);
            return UlDistance < Math.Pow(MaximumDistanceToRecogniseOnClick + this.BrushSize / 2, 2);
        }

        public override IVisualObject Clone()
        {
            Point start, end;
            if (direction) // If this inverts the line, use !direction.
            {
                start = this.Location;
                end = new Point(this.Location.X, this.Location.Y);
            }
            else
            {
                start = new Point(this.Location.X + this.Size.Width, this.Location.Y);
                end = new Point(this.Location.X, this.Location.Y + this.Size.Height);
            }
            return new LineSegment(start, end);
        }

        public override void BurnOn(Image im)
        {
            Graphics gr = Graphics.FromImage(im);
            Point begin, end;
            if (direction)
            {
                begin = new Point(this.Location.X, this.Location.Y + sySize);
                end = new Point(this.Location.X + sxSize, this.Location.Y);
            }
            else
            {
                begin = this.Location;
                end = this.Location + this.Size;
            }
            using (Pen p = new Pen(this.Colour, this.BrushSize))
                gr.DrawLine(p, begin, end);
        }

        public override void ResizeToBeIn(Point p1, Point p2)
        {
            base.ResizeToBeIn(p1, p2);
            direction = DetermineDirection(p1, p2);
        }
        #endregion

        #region private methods
        void SetBeginAndEnd(Point s, Point e)
        {
            SetBoxAround(s, e);
            this.direction = DetermineDirection(s, e);
        }

        static bool DetermineDirection(Point p1, Point p2)
        {
            return (p1.X < p2.X) != (p1.Y < p2.Y);
        }
        #endregion
    }
}
